abstract Char: Void {
    public function capitalize(): Char {
        if this >= c'a' && this <= c'z' {
            return this + c'A' - c'a';
        } else {
            return this;
        }
    }

    public static function minValue(): Self {
        return ${SCHAR_MIN: Self};
    }
    public static function maxValue(): Self {
        return ${SCHAR_MAX: Self};
    }
}
abstract Size: Void {
    public static function minValue(): Self {
        return 0;
    }
    public static function maxValue(): Self {
        return ${SIZE_MAX: Self};
    }
}

abstract Int[$W = 0]: Void {
    public static function minValue(): Self {
        static if W == 0 {
            return ${INT_MIN: Self};
        } else {
            return ~(Self.maxValue());
        }
    }
    public static function maxValue(): Self {
        static if W == 0 {
            return ${INT_MAX: Self};
        } else {
            return ~(0 as Self) & ~((1 as Self << (W - 1)) as Self);
        }
    }
}

abstract Uint[$W = 0]: Void {
    public static function minValue(): Self {
        return 0;
    }
    public static function maxValue(): Self {
        static if W == 0 {
            return ${UINT_MAX: Self};
         } else {
             return (1 as Self << (W - 1) as Self) | ~(1 as Self << (W - 1) as Self);
         }
    }
}

#[demote] abstract Float[$W = 32]: Void {
    public static function minValue(): Self {
        static if W == 32 {
            return ${FLT_MIN: Self};
        } else if W == 64 {
            return ${DBL_MIN: Self};
        } else {
            panic("invalid float width!");
        }
    }

    public static function maxValue(): Self {
        static if W == 32 {
            return ${FLT_MAX: Self};
        } else if W == 64 {
            return ${DBL_MAX: Self};
        } else {
            panic("invalid float width!");
        }
    }
}

typedef Int8 = Int[8];
typedef Int16 = Int[16];
typedef Int32 = Int[32];
typedef Int64 = Int[64];
typedef Uint8 = Uint[8];
typedef Uint16 = Uint[16];
typedef Uint32 = Uint[32];
typedef Uint64 = Uint[64];
typedef Float32 = Float[32];
typedef Float64 = Float[64];

typedef Byte = Int8;
typedef Short = Int16;
typedef Long = Int64;
typedef Double = Float64;

/**
 * A type which represents only integer values.
 */
#[closed] trait Integral;

/**
 * A type which represents numbers with integral and fractional components.
 *
 * Numeric literals with decimal points and no type annotation are constrained
 * to types implementing this trait; if none is specified, Float will be used.
 */
#[closed] trait NumericMixed;

/**
 * Any numeric type.
 *
 * Numeric literals without a decimal point or type annotation are constrained
 * to types implementing this trait; if none is specified, Int will be used.
 */
#[closed] trait Numeric;

// The following traits classify whether specific numbers are representable by
// each of the builtin numeric types. They generally don't need to be directly
// used, but will be inferred as literal constraints to let us default to an
// appropriate numeric type.
#[closed] trait NumericNE63;
default NumericNE63 as Int64;
#[closed] trait NumericNE53;
default NumericNE53 as Int64;
#[closed] trait NumericNE31;
default NumericNE31 as Int;
#[closed] trait NumericNE24;
default NumericNE24 as Int;
#[closed] trait NumericNE15;
default NumericNE15 as Int;
#[closed] trait NumericNE7;
default NumericNE7 as Int;
#[closed] trait NumericE7;
default NumericE7 as Int;
#[closed] trait NumericE8;
default NumericE8 as Int;
#[closed] trait NumericE15;
default NumericE15 as Int;
#[closed] trait NumericE16;
default NumericE16 as Int;
#[closed] trait NumericE24;
default NumericE24 as Int;
#[closed] trait NumericE31;
default NumericE31 as Int;
#[closed] trait NumericE32;
default NumericE32 as Int64;
#[closed] trait NumericE53;
default NumericE53 as Int64;
#[closed] trait NumericE63;
default NumericE63 as Int64;
#[closed] trait NumericE64;
default NumericE64 as Uint64;

// By-definition implementations; E8 must be able to represent [0,2e8), etc.
implement NumericNE7 for NumericNE15;
implement NumericNE15 for NumericNE24;
implement NumericNE24 for NumericNE31;
implement NumericNE31 for NumericNE53;
implement NumericNE53 for NumericNE63;
implement NumericE7 for NumericE8;
implement NumericE8 for NumericE15;
implement NumericE15 for NumericE16;
implement NumericE16 for NumericE24;
implement NumericE24 for NumericE31;
implement NumericE31 for NumericE32;
implement NumericE32 for NumericE53;
implement NumericE53 for NumericE63;
implement NumericE63 for NumericE64;

// Slot our existing numeric types into the appropriate size classes.
implement Integral for Int8;
implement NumericNE7 for Int8;
implement NumericE7 for Int8;
implement Integral for Int16;
implement NumericNE15 for Int16;
implement NumericE15 for Int16;
implement Integral for Int32;
implement NumericNE31 for Int32;
implement NumericE31 for Int32;
implement Integral for Int64;
implement NumericNE63 for Int64;
implement NumericE63 for Int64;
implement Integral for Uint8;
implement NumericE8 for Uint8;
implement Integral for Uint16;
implement NumericE16 for Uint16;
implement Integral for Uint32;
implement NumericE32 for Uint32;
implement Integral for Uint64;
implement NumericE64 for Uint64;
implement NumericMixed for Float32;
implement NumericNE24 for Float32;
implement NumericE24 for Float32;
implement NumericMixed for Float64;
implement NumericNE53 for Float64;
implement NumericE53 for Float64;

implement NumericE8 for Char;
implement NumericNE7 for Char;
implement Integral for Char;
implement NumericE31 for Int;
implement NumericNE31 for Int;
implement Integral for Int;
implement NumericE32 for Uint;
implement Integral for Uint;
implement NumericE16 for Size;
implement Integral for Size;

implement Numeric for Integral;
implement Numeric for NumericMixed;

// For literals one of the above specializations will take precedence; these
// would only be used when we know nothing else about the value, and this are
// pretty arbitrary. Erring toward smaller size at the cost of possible more
// frequent overflow issues.
default Integral as Int;
default NumericMixed as Float;
default Numeric as Int;
